package com.icloud.wallet.boot.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.icloud.common.cache.constants.CacheConstants;
import com.icloud.common.cache.service.RedisService;
import com.icloud.common.core.api.ResultCode;
import com.icloud.common.core.constant.CommonConstant;
import com.icloud.common.core.utils.BigDecimalUtils;
import com.icloud.common.database.enums.*;
import com.icloud.common.database.mapper.AppCustomerMapper;
import com.icloud.common.database.mapper.CustomerWalletMapper;
import com.icloud.common.database.model.AppCustomer;
import com.icloud.common.database.model.CustomerWallet;
import com.icloud.common.web.enums.StateEnum;
import com.icloud.common.web.exception.Asserts;
import com.icloud.common.web.util.AuthUtil;
import com.icloud.wallet.boot.dto.QueryRedPacketDto;
import com.icloud.wallet.boot.dto.SendRedPacketDto;
import com.icloud.common.database.mapper.RedPacketMapper;
import com.icloud.common.database.model.RedPacket;
import com.icloud.common.database.model.RedPacketReceive;
import com.icloud.common.database.mapper.TWalletJournalMapper;
import com.icloud.common.database.model.TWalletJournal;
import com.icloud.wallet.boot.service.RedPacketService;
import com.icloud.wallet.boot.vo.AccRedPacketVo;
import com.icloud.wallet.boot.vo.RedPacketVo;
import com.icloud.wallet.boot.vo.SendRedPacketVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.icloud.common.cache.enums.RedisConstantEnum;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;


@Slf4j
@Service
public class RedPacketServiceImpl extends ServiceImpl<RedPacketMapper, RedPacket> implements RedPacketService {
    @Autowired
    private AuthUtil authUtil;
    @Autowired
    private RedisService redisService;
    @Autowired
    private AppCustomerMapper appCustomerMapper;
    @Autowired
    private CustomerWalletMapper customerWalletMapper;
    @Autowired
    private TWalletJournalMapper tWalletJournalMapper;

    /**
     * 发红包
     * @param dto
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public SendRedPacketVo send(SendRedPacketDto dto) {
        Asserts.fail(BigDecimalUtils.lte(dto.getAmount(), BigDecimal.ZERO), ResultCode.RED_PACKET_AMOUNT_NOT_LTE_ZERO);
        BigDecimal amount = dto.getAmount();
        String rpType = dto.getType();
        String title = dto.getTitle();

        //红包数量,红包类型为拼手气红包、普通红包时必须大于0
        Integer number = dto.getNumber();
        if(RedPacketTypeEnum.isRandom(rpType) || RedPacketTypeEnum.isNormal(rpType)){
            Asserts.fail(null == number || CommonConstant.ZERO.equals(number), ResultCode.RED_PACKET_SIZE_NOT_NULL);
        }
        if(number==null || CommonConstant.ZERO.equals(number)){
            number = 1;
        }
        //红包领取人id, 单聊普通红包或专属红包时必须存在
        Long receiveId = dto.getReceiveId();
        if(RedPacketTypeEnum.isExclusive(rpType) || RedPacketTypeEnum.isSingleNormal(rpType)){
            Asserts.fail(null == receiveId, ResultCode.RED_PACKET_TO_USER_NOT_NULL);
            Asserts.fail(1!=number, ResultCode.RED_PACKET_NUMBER_MUST_BE_ONE);
        }
        AppCustomer receiver = appCustomerMapper.selectById(receiveId);
        Asserts.fail(null == receiver, ResultCode.RED_PACKET_TO_USER_NOT_NULL);

        //群id，红包类型为群专属红包、拼手气红包、群普通红包时必须存在
        String groupId = dto.getGroupId();
        if(RedPacketTypeEnum.isRandom(rpType) || RedPacketTypeEnum.isExclusive(rpType) || RedPacketTypeEnum.isNormal(rpType)){
            Asserts.fail(StringUtils.isBlank(groupId), ResultCode.RED_PACKET_CHAT_ID_NOT_NULL);
        }
        // 群普通红包的金额是单个红包的金额还是总金额(目前只做领取人只有一个人的红包,这个可以暂时不管)
        Long loginId = authUtil.getUserId();
        LambdaQueryWrapper<CustomerWallet> walletQuery = new LambdaQueryWrapper<>();
        walletQuery.eq(CustomerWallet::getCustomerId, loginId);
        CustomerWallet wallet = customerWalletMapper.selectOne(walletQuery);
        Asserts.fail(null==wallet, ResultCode.DATA_NOT_EXISTS);

        BigDecimal beforeBal = wallet.getBcpc();
        BigDecimal afterBal = beforeBal.subtract(amount);
        Asserts.fail(afterBal.compareTo(BigDecimal.ZERO)<0, ResultCode.INSUFFICIENT_BALANCE);

        //生成红包
        RedPacket redPacket = new RedPacket();
        redPacket.setTitle(title);
        redPacket.setCreatedId(loginId);
        redPacket.setType(rpType);
        redPacket.setGroupId(groupId);
        redPacket.setNumber(number);
        redPacket.setAmount(amount);
        redPacket.setHandleState(RedPacketHandleStateEnum.UNHANDLED.getCode());
        redPacket.setState(StateEnum.NORMAL.getCode());
        redPacket.setReceiveId(receiveId);
        save(redPacket);

        Long rpId = redPacket.getId();

        // 生成流水
        TWalletJournal rewardJournal = TWalletJournal.builder()
                .customerId(loginId)
                .journalType(JournalTypeEnum.SEND_RED_PKT.getCode())
                .beforeBalance(beforeBal)
                .amount(amount)
                .afterBalance(afterBal)
                .currency(CurrencyEnum.BCPC.getCode())
                .direction(DirectionEnum.CREDIT.getCode())
                .journalState(JournalStateEnum.FINISHED.getCode())
                .remark("派发红包流水,红包Id:"+rpId)
                .createdId(loginId)
                .updatedId(loginId)
                .build();
        tWalletJournalMapper.insert(rewardJournal);

        // 修改钱包余额
        CustomerWallet walletUpdate = CustomerWallet.builder()
                .id(wallet.getId())
                .bcpc(afterBal)
                .build();
        customerWalletMapper.updateById(walletUpdate);

        // 红包有效期
        String key = CacheConstants.CACHE_RED_PACKET + redPacket.getId();
        redisService.set(key, amount.toPlainString(), CacheConstants.CACHE_RED_PACKET_VALIDITY_SECONDS);
        //redisService.set(key, amount.toPlainString(), 60);

        return new SendRedPacketVo(redPacket);
    }

    @Override
    public AccRedPacketVo query(QueryRedPacketDto dto) {
        Long loginId = authUtil.getUserId();
        Long customerId = dto.getCustomerId();

        String transport = dto.getTransport();
        RedPacketTransportEnum redPacketTransportEnum = RedPacketTransportEnum.byCode(transport);
        Asserts.fail(redPacketTransportEnum==null, ResultCode.VALIDATE_FAILED);

        String yearStr = dto.getYear();
        Integer year = Integer.valueOf(yearStr);
        LocalDateTime start = LocalDateTime.of(year, 1, 1, 0, 0, 0);
        LocalDateTime end = LocalDateTime.of(year+1, 1, 1, 0, 0, 0);
        end = end.minusSeconds(1);

        AccRedPacketVo vo = new AccRedPacketVo();
        vo.setYear(yearStr);
        vo.setTransport(transport);
        vo.setAccAmount(BigDecimal.ZERO);

        LambdaQueryWrapper<RedPacket> rpQuery = new LambdaQueryWrapper<>();
        if(RedPacketTransportEnum.isSend(transport)) {
            rpQuery.eq(RedPacket::getCreatedId, customerId);
        }
        if(RedPacketTransportEnum.isReceive(transport)) {
            rpQuery.eq(RedPacket::getReceiveId, customerId);
        }
        rpQuery.eq(RedPacket::getHandleState, RedPacketHandleStateEnum.HANDLED);
        rpQuery.between(RedPacket::getCreatedAt, start, end);
        List<RedPacket> rpList = list(rpQuery);
        if(CollectionUtils.isEmpty(rpList)){
            return vo;
        }
        // 目前只有一对一的红包,领取人直接从redpacket中拿
        List<Long> custIdList = null;
        if(RedPacketTransportEnum.isSend(transport)) {
            custIdList = rpList.stream().map(RedPacket::getCreatedId).collect(Collectors.toList());
        }
        if(RedPacketTransportEnum.isReceive(transport)) {
            custIdList = rpList.stream().map(RedPacket::getReceiveId).collect(Collectors.toList());
        }
        LambdaQueryWrapper<AppCustomer> custQuery = new LambdaQueryWrapper<>();
        custQuery.in(AppCustomer::getId, custIdList);
        custQuery.eq(AppCustomer::getState, StateEnum.NORMAL.getCode());
        List<AppCustomer> custList = appCustomerMapper.selectList(custQuery);
        Map<Long, AppCustomer> custMap = custList.stream().collect(Collectors.toMap(AppCustomer::getId, Function.identity()));

        BigDecimal acc = BigDecimal.ZERO;
        List<RedPacketVo> voList = new ArrayList<>();
        for(RedPacket rp : rpList) {
            AppCustomer cust = null;
            if (RedPacketTransportEnum.isSend(transport)) {
                cust = custMap.get(rp.getCreatedId());
            }
            if (RedPacketTransportEnum.isReceive(transport)) {
                cust = custMap.get(rp.getReceiveId());
            }
            Long custId = cust.getId();
            String nickname = cust.getNickname();
            RedPacketVo rpVo = new RedPacketVo();
            BigDecimal amt = rp.getAmount();
            rpVo.setCustomerId(custId);
            rpVo.setNickname(nickname);
            rpVo.setTransportAmount(amt);
            rpVo.setCreatedAt(rp.getCreatedAt());
            acc = acc.add(amt);
            voList.add(rpVo);
        }

        vo.setAccAmount(acc);
        vo.setVoList(voList);
        return vo;
    }

    /**
     * 创建红包领取列表
     */
    public void createRedPacketReceive(Long redPacketId, BigDecimal amount, Integer size, String type){
        BigDecimal total = amount;
        BigDecimal remainMoney = total;

        List<RedPacketReceive> list = new ArrayList<>();
        for (int i = size; i > 0; i--){
            RedPacketReceive model = new RedPacketReceive();
            model.setRedPacketId(redPacketId);
            model.setCreatedId(authUtil.getUserId());
            list.add(model);

            //如果是拼手气红包，则使用两倍均值法根据红包数量计算红包金额
            if(RedPacketTypeEnum.RANDOM.getCode().equals(type)){
                BigDecimal randomMoney = getRandomMoney(i, remainMoney, 2,total,size);
                remainMoney = BigDecimalUtils.subtract(remainMoney,randomMoney);
                model.setAmtReceived(randomMoney);
            }else{
                model.setAmtReceived(total);
            }
        }
        redisService.set(RedisConstantEnum.RED_PACKET_RECEIVE_LIST.getValue() + redPacketId,list);
    }

    /**
     * 两倍均值法根据红包数量计算红包金额
     * @param size 剩余的红包数量
     * @param remainMoney 剩余的钱
     * @return
     */
    public static BigDecimal getRandomMoney(Integer size,BigDecimal remainMoney,int scale,BigDecimal total,int totalSize) {
        int remainSize = size;
        if (remainSize == 1) {
            MathContext mathContext = new MathContext(0, RoundingMode.HALF_UP);
            BigDecimal round = BigDecimalUtils.multiply(remainMoney, new BigDecimal(100)).round(mathContext);
            return  BigDecimalUtils.divide(round ,new BigDecimal(100),scale,RoundingMode.HALF_UP);
        }
        Random r = new Random();

        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("0.");
        for(int a = 1;a < scale; a++){
            stringBuilder.append("0");
        }
        stringBuilder.append("1");

        BigDecimal min   = new BigDecimal(stringBuilder.toString()); //最小值

        BigDecimal avg = BigDecimalUtils.divide(total, new BigDecimal(totalSize), scale + 1, RoundingMode.HALF_UP);
        if(BigDecimalUtils.eq(min,avg)){
            return min;
        }

        BigDecimal max   = BigDecimalUtils.multiply(BigDecimalUtils.divide(remainMoney , new BigDecimal(size),scale,RoundingMode.HALF_UP) ,new BigDecimal(2));
        BigDecimal money = BigDecimalUtils.multiply(BigDecimal.valueOf(r.nextDouble()),max);

        MathContext mathContext = new MathContext(0, RoundingMode.DOWN);
        money = BigDecimalUtils.divide(BigDecimalUtils.multiply(money,new BigDecimal(100)).round(mathContext),new BigDecimal(100),scale,RoundingMode.HALF_UP);
        money = BigDecimalUtils.gte(money , max) ? max: money;
        money = BigDecimalUtils.lte(money , min) ? min: money;


        return money;
    }
}
